package buaa.qel.rpc.client.loadbalance.impl;

import buaa.qel.rpc.client.handler.RpcClientHandler;
import buaa.qel.rpc.client.loadbalance.RpcLoadBalance;
import buaa.qel.rpc.common.info.RegistryNodeInfo;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 选择访问次数最低的注册地址，如果符合条件的地址有多个，选择加入时间最早的（最旧的）地址
 */
public class RpcLoadBalanceLFU extends RpcLoadBalance {
    private ConcurrentMap<String, HashMap<RegistryNodeInfo, Integer>> jobLfuMap = new ConcurrentHashMap<>();
    private long CACHE_VALID_TIME = 0;

    public RegistryNodeInfo doRoute(String serviceKey, List<RegistryNodeInfo> addressList) {
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLfuMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000 * 60 * 60 * 24;
        }
        /**
         * 每个服务维护一个哈希表，key为地址，value为选择次数
         */
        HashMap<RegistryNodeInfo, Integer> lfuItemMap = jobLfuMap.get(serviceKey);
        if (lfuItemMap == null) {
            lfuItemMap = new HashMap<RegistryNodeInfo, Integer>();
            jobLfuMap.putIfAbsent(serviceKey, lfuItemMap);   // 避免重复覆盖
        }

        /**
         * 一个地址不存在缓存中或已被选择了一百万次以上，则添加新记录0次
         */
        for (RegistryNodeInfo address : addressList) {
            if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) > 1000000) {
                lfuItemMap.put(address, 0);
            }
        }

        /**
         * 删除已下线地址
         */
        List<RegistryNodeInfo> delKeys = new ArrayList<>();
        for (RegistryNodeInfo existKey : lfuItemMap.keySet()) {
            if (!addressList.contains(existKey)) {
                delKeys.add(existKey);
            }
        }
        if (delKeys.size() > 0) {
            for (RegistryNodeInfo delKey : delKeys) {
                lfuItemMap.remove(delKey);
            }
        }

        /**
         * 选择使用频率最少的？ 可能有问题
         */
        List<Map.Entry<RegistryNodeInfo, Integer>> lfuItemList = new ArrayList<Map.Entry<RegistryNodeInfo, Integer>>(lfuItemMap.entrySet());
        Collections.sort(lfuItemList, new Comparator<Map.Entry<RegistryNodeInfo, Integer>>() {
            @Override
            public int compare(Map.Entry<RegistryNodeInfo, Integer> o1, Map.Entry<RegistryNodeInfo, Integer> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });

        Map.Entry<RegistryNodeInfo, Integer> addressItem = lfuItemList.get(0);
        RegistryNodeInfo minAddress = addressItem.getKey();
        addressItem.setValue(addressItem.getValue() + 1);

        return minAddress;
    }

    @Override
    public RegistryNodeInfo route(String serviceKey, Map<RegistryNodeInfo, RpcClientHandler> connectedServerNodes) throws Exception {
        Map<String, List<RegistryNodeInfo>> serviceMap = getServiceMap(connectedServerNodes);
        List<RegistryNodeInfo> addressList = serviceMap.get(serviceKey);
        if (addressList != null && addressList.size() > 0) {
            return doRoute(serviceKey, addressList);
        } else {
            throw new Exception("Can not find connection for service: " + serviceKey);
        }
    }
}
